let videoDecodeType,
  audioBitRate,
  hostname,
  streamName,
  app,
  stream,
  key,
  takeSdp;

// type: 'publish' || 'play'
const getNodeUrl = ({hostname = '', type = 'publish', app, stream}) =>
  `https://${hostname}/v1/webrtc/getnodes/${app}/${stream}/${type}`;

// type: 'publish' || 'play'
const getRemoteUrl = ({hostname = '', type = 'publish', app, stream}) =>
  `https://${hostname}/v1/webrtc/sdp/${app}/${stream}/${type}`;

const getQueryString = (name) => {
  var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
  var r = window.location.search.substr(1).match(reg);
  if (r != null) return unescape(r[2]);
  return null;
};

const RTCOfferOptions = {
  offerToReceiveAudio: 1,
  offerToReceiveVideo: 1,
};

let $localVideo = $('#local-video').get(0);
let $remoteVideo = $('#remote-video').get(0);

$('#setup').click(() => {
  videoDecodeType = $('#videoCodeType').val();
  audioBitRate = parseInt($('#audioBitrate').val(), 10);
  hostname = $('#hostname').val();
  streamName = $('#stream').val();
  app = $('#app').val();
  key = $('#key').val();
  takeSdp = $('#takeSdp').prop('checked');
});

$('#publish').click(async () => {
  try {
    if (!key.trim()) {
      return alert('请输入 key');
    }

    stream = await navigator.mediaDevices.getUserMedia({
      audio: true,
      video: true,
    });
    $localVideo.srcObject = stream;

    try {
      const pc = createPC();
      // ! addTrack before createOffer
      stream.getTracks().forEach((track) => pc.addTrack(track, stream));
      const offer = await pc.createOffer(RTCOfferOptions);

      const type = 'publish';
      const formattedOffer = formatOffer(offer, type);
      await handlePC(pc, formattedOffer, type);
    } catch (e) {
      errorHandle('createOffer', e);
    }
  } catch (e) {
    errorHandle('getUserMedia', e);
  }
});

$('#play').click(async function () {
  try {
    const pc = createPC();

    pc.addEventListener('track', (e) => {
      // run twice
      if ($remoteVideo.srcObject !== e.streams[0]) {
        $remoteVideo.srcObject = e.streams[0];
      }
    });

    const offer = await pc.createOffer(RTCOfferOptions);
    const type = 'play';
    const formattedOffer = formatOffer(offer, type);
    await handlePC(pc, formattedOffer, type);
  } catch (e) {
    errorHandle('createOffer', e);
  }
});

$('#stop').click(function () {
  stream.getTracks().forEach((track) => track.stop());
});

function createPC() {
  const pc = new RTCPeerConnection();
  pc.addEventListener('icecandidate', onIceCandidate);
  pc.addEventListener('iceconnectionstatechange', (e) =>
    onIceStateChange(pc, e)
  );
  return pc;
}

function formatOffer(offer, type = 'publish') {
  offer.sdp = offer.sdp.replace(/sendrecv/g, 'sendonly');
  if (type === 'publish') {
    offer.sdp = offer.sdp.replace(
      /useinbandfec=\d+/,
      'maxaveragebitrate=' + audioBitRate
    );
  }

  offer.sdp = formatSdpByDecodeType(offer.sdp, videoDecodeType);
  return offer;
}

// type: 'publish' || 'play';
async function handlePC(pc, offer, type) {
  try {
    await pc.setLocalDescription(offer);
    let getNodesUrl = getNodeUrl({
      hostname,
      type,
      app,
      stream: streamName,
    });

    let signData = getSign(takeSdp ? offer : '', type, app, streamName, key);

    // 灰度的节点：新增 test_gw_nodeip 参数，仅供测试使用。
    let test_gw_nodeip = getQueryString('test_gw_nodeip');
    if (test_gw_nodeip) {
      signData.test_gw_nodeip = test_gw_nodeip;
    }

    getNodesUrl = `${getNodesUrl}?${normalizeParams(signData)}`;
    // 调取服务端接口获取 node 节点
    const {
      data: {nodes, answer},
      serverdata,
    } = await ajaxPost(
      getNodesUrl,
      takeSdp
        ? {
            offer: {
              sdp: offer.sdp,
            },
          }
        : ''
    );
    log('nodes', nodes.length);

    let length = nodes.length;
    if (!length) {
      log('nodes', '没有可用的 IP');
      return;
    }

    if (answer && answer.sdp) {
      // takeSdp 为 true 时，如果返回的参数有 sdp，可以完成信令交换
      setRemoteDescription(pc, answer.sdp);
    } else {
      // 返回的参数没有 sdp，则需要向返回的 nodes 节点发送 sdp 获取返回的 sdp，才可以完成信令交换

      // todo: 重试逻辑，如果节点失败，则尝试下一个节点，直至某一节点尝试成功或全部节点尝试失败
      getAnswerSdp({ip: nodes[0].key, offer, type, serverdata}).then((data) => {
        const {data: remoteDescription} = data;
        log('remoteDescription', remoteDescription);

        setRemoteDescription(pc, remoteDescription.answer.sdp);
      });
    }
  } catch (e) {
    errorHandle('setLocalDescription', e);
  }

  function getAnswerSdp({ip, offer, type, serverdata}) {
    log('IP', ip);

    let remoteUrl = getRemoteUrl({
      hostname,
      type,
      app,
      stream: streamName,
    });
    const getRemoteUrlData = {
      node_key: ip,
      offer: {
        sdp: offer.sdp,
      },
      serverdata,
    };
    let signData = getSign(offer, type, app, streamName, key);

    // 灰度的节点：新增 test_gw_nodeip 参数，仅供测试使用。
    let test_gw_nodeip = getQueryString('test_gw_nodeip');
    if (test_gw_nodeip) {
      signData.test_gw_nodeip = test_gw_nodeip;
    }

    remoteUrl = `${remoteUrl}?${normalizeParams(signData)}`;

    return ajaxPost(remoteUrl, getRemoteUrlData);
  }
  return pc;
}

function onIceCandidate(event) {
  log('ICE candidate', event.candidate ? event.candidate.candidate : null);
}

function onIceStateChange(pc, event) {
  log('ICE state', pc.iceConnectionState);
  log('ICE state change event', event);
}

function formatSdpByDecodeType(sdp, type) {
  const H264 = [];
  const H265 = [];
  const VP8 = [];
  const VP9 = [];
  const OHTER = [];
  const videoSdp = /m=video.+/.exec(sdp)[0];
  const videoHead = videoSdp.match(/[\s|\d]+/g)[1].replace(' ', '');
  const videoDecodeTypesArr = videoHead.split(' ');

  videoDecodeTypesArr.forEach((decodeType) => {
    let reg = new RegExp('a=rtpmap:' + decodeType + '.+');
    let matched = reg.exec(sdp)[0];
    if (matched.includes('H264')) {
      H264.push(decodeType);
    } else if (matched.includes('H265')) {
      H265.push(decodeType);
    } else if (matched.includes('VP8')) {
      VP8.push(decodeType);
    } else if (matched.includes('VP9')) {
      VP9.push(decodeType);
    } else {
      OHTER.push(decodeType);
    }
  });

  OHTER.forEach((otherType) => {
    let reg = new RegExp('a=fmtp:' + otherType + '.+apt=(\\d+)');
    let matchedArr = reg.exec(sdp);
    let matched = matchedArr && matchedArr[1];
    if (matched) {
      if (H264.includes(matched)) {
        H264.push(otherType);
      } else if (H265.includes(matched)) {
        H265.push(otherType);
      } else if (VP8.includes(matched)) {
        VP8.push(otherType);
      } else if (VP9.includes(matched)) {
        VP9.push(otherType);
      }
    }
  });

  let targetArr = [];
  if (type === 'VP9') {
    targetArr = [].concat(H265, H264, VP8);
  } else if (type === 'VP8') {
    targetArr = [].concat(H265, H264, VP9);
  } else if (type === 'H264') {
    targetArr = [].concat(H265, VP8, VP9);
  } else if (type === 'H265') {
    targetArr = [].concat(VP8, H264, VP9);
  }

  targetArr.forEach((itype) => {
    let currentIndex = videoDecodeTypesArr.indexOf(itype);
    videoDecodeTypesArr.splice(currentIndex, 1);

    let regRtpmap = new RegExp('a=rtpmap:' + itype + '.+\\s\\n', 'g');
    let regRtcpfb = new RegExp('a=rtcp-fb:' + itype + '.+\\s\\n', 'g');
    let regFmtp = new RegExp('a=fmtp:' + itype + '.+\\s\\n', 'g');

    sdp = sdp.replace(regRtpmap, '');
    sdp = sdp.replace(regRtcpfb, '');
    sdp = sdp.replace(regFmtp, '');
  });

  return sdp.replace(videoHead, videoDecodeTypesArr.join(' '));
}

/**
 * 签名生成
 * @param {string} offer 包含 sdp 的 offer
 * @param {string} type 生成签名的类型，'publish' || 'play'
 * @param {string} app 应用的 app
 * @param {string} stream 推拉流的名称
 * @param {string} key 由服务端和客户点提前约定好
 */
function getSign(offer, type, app, stream, key) {
  const time = Date.now();
  const nonce = (Date.now() + Math.random()).toString(32);
  const paramData = {
    offer: offer ? offer.sdp : '',
    time,
    nonce,
    app,
    stream,
    type,
  };
  const signStr = normalizeParams(paramData) + `&key=${key}`;
  log('get sign string', signStr);
  const sign = md5(signStr);
  log('sign', sign);

  return {
    time,
    nonce,
    sign,
  };
}

// 创建本地连接，提供 offer
function ajaxPost(url, data) {
  return $.ajax({
    type: 'post',
    url,
    crossDomain: true,
    data: data ? JSON.stringify(data) : '',
    contentType: 'application/json',
    dataType: 'json',
  });
}

function errorHandle(name, e) {
  console.warn(`${name} =>`);
  console.error(e);
}

function log(name, msg) {
  console.warn(`${name} =>`);
  console.log(msg);
}

/**
 * 将参数按照 ASCII 码从小到大排序，并生成 (key=value&key1=value1) 格式的字符串
 * @param {object} data 对象
 * @return {string} 生成 (key=value&key1=value1) 格式的字符串
 */
function normalizeParams(data) {
  return Object.keys(data)
    .sort()
    .map((key) => `${key}=${data[key]}`)
    .join('&');
}

function setRemoteDescription(pc, sdp) {
  const answerDescription = {
    type: 'answer',
    sdp,
    toJSON: () => {},
  };
  log('answerDescription', answerDescription);

  pc.setRemoteDescription(new RTCSessionDescription(answerDescription))
    .then(() => {
      log('setRemoteDescription', 'setRemoteDescription success');
    })
    .catch((e) => {
      errorHandle('setRemoteDescription', e);
    });
}
